feat(bundles): metapackage split (Phase A) — engine-only lfx, lfx-bundles long tail, 5 graduated partner packages#13563
feat(bundles): metapackage split (Phase A) — engine-only lfx, lfx-bundles long tail, 5 graduated partner packages#13563erichare wants to merge 19 commits into
Conversation
… tier Foundation for the bundle metapackage split (1.11). Adds a third @official-slot discovery source: a distribution declaring the [project.entry-points."lfx.bundles"] group ships a package whose immediate subdirectories are each a manifest-less bundle, folder-walked and registered at @official with no extension.json (the langchain-community model). - new loader/_bundles_root.py::load_lfx_bundles_extensions, mirroring discover_inline_bundles but sourcing roots from the lfx.bundles entry-point group via find_spec and reusing _load_bundle_directory - discovery precedence becomes installed > seed > lfx_bundles > dev > inline so a manifest-shipping lfx-<provider> always shadows the same-named provider in the metapackage (lets a provider graduate with no lockstep release), emitting the existing bundle-shadowed warning - new bundle-discovery-malformed warning code, emitted on warnings (never flips ok / aborts startup) for unresolvable declarations and invalid provider folder names - exported via loader/__init__ and the lfx.extension PEP-562 lazy surface 10 new tests in test_load_lfx_bundles.py; full extension unit suite (449) passes; ruff + format clean; mypy clean on the new module.
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR adds manifest-less bundle discovery for the ChangesManifest-less Bundle Discovery
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 8 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (8 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ndation-discovery
The BUNDLE_API.md changelog gate diffs each PR's branch against main, so the entry covering this PR's surface additions (lfx.bundles discovery, the lfx_bundles precedence tier, bundle-discovery-malformed) must live on this branch, not only further up the stack. Text is verbatim from the graduate-partners commit so the stacked merge dedupes trivially.
…ndation-discovery
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@BUNDLE_API.md`:
- Around line 405-421: Update the BUNDLE_API.md wording for manifest-less
lfx.bundles to avoid implying the CLI can validate those directories: change
“exempt from `lfx extension validate`” to something like “not validated / not
intended as input to `lfx extension validate`” and add a one-line explanation
that validate_extension() (the CLI check) requires an extension.json or
[tool.langflow.extension] in pyproject.toml so pointing the validator at a
manifest-less provider will report “manifest-not-found”; reference the loader
module src/lfx/src/lfx/extension/loader/_bundles_root.py and the
validate_extension() CLI behavior in the note.
In `@src/lfx/src/lfx/extension/loader/_bundles_root.py`:
- Around line 246-260: _spec_package_dir currently treats plain module specs as
package roots by returning Path(spec.origin).parent when
submodule_search_locations is absent; change it to reject non-package
entry-point targets by removing the fallback that returns the origin's parent.
Update _spec_package_dir so it only returns a Path when
spec.submodule_search_locations is present (i.e., a package or namespace
package) and otherwise returns None; keep the initial spec is None guard and do
not treat spec.origin as a package root.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: adefeccc-6f5e-4660-b680-b1263385f836
📒 Files selected for processing (8)
BUNDLE_API.mdsrc/lfx/src/lfx/extension/__init__.pysrc/lfx/src/lfx/extension/errors.pysrc/lfx/src/lfx/extension/loader/__init__.pysrc/lfx/src/lfx/extension/loader/_bundles_root.pysrc/lfx/src/lfx/interface/components.pysrc/lfx/tests/unit/extension/loader/test_load_lfx_bundles.pysrc/lfx/tests/unit/extension/test_errors.py
…date wording CodeRabbit review fixes on #13563: - _spec_package_dir no longer falls back to a plain module's parent directory -- a single-file entry-point target now emits the bundle-discovery-malformed warning instead of folder-walking unrelated sibling directories as bundles (+ regression test) - BUNDLE_API.md / loader docstring no longer say manifest-less providers are 'exempt from lfx extension validate'; they are not valid validator input (validate requires a manifest and reports manifest-not-found)
This comment has been minimized.
This comment has been minimized.
Commit 9df9475 ('Frontend test updates') accidentally reverted both files to their pre-#9902 state, removing the no_env_fallback contract in load_from_env_vars / update_table_params_with_load_from_db_fields, the defensive params.pop('code') in build_custom_component, and the localValue/updateGlobalVariableCell handling in tableAutoCellRender. The base branch's regression tests (test_loading_no_env_fallback.py, test_loading_custom_component_code_param.py, tableAutoCellRender index.test.tsx) were kept, so the reversion failed them.
- Move the freeze-components job from lint-py.yml (workflow_call/ workflow_dispatch only -- never invoked, so the gate could not block a PR) into extension-migration-checks.yml, which already triggers on pull_request for src/lfx/src/lfx/components/**. Add the gate's script and baseline to the path filter. - Add timeout-minutes to the cross-bundle-test jobs so a hung install or test leg cannot occupy a runner for the 360-minute default. - _discover_shimmed_component_dirs: read only the first line of each __init__.py instead of the whole file -- the shim marker contract is line 1 (enforced by test_shim_source_contract), and this prevents a non-shim file with the marker after leading blank lines from being misclassified and silently skipped from the in-tree walk.
This comment has been minimized.
This comment has been minimized.
… starter projects Saved flows reference moved providers by their legacy palette identity -- either the bare class name (TavilySearchComponent) or the component's name attribute (AstraDB, Chroma, needle, OllamaModel). After the provider move neither resolved to a current template on the backend: - lfx.utils.component_aliases now derives the bare class name (and its Component-stripped form) from ext:<bundle>:<Class>@<slot> keys, mirroring the frontend's getTemplateAliases. Ext templates carry name=None / _type='Component', so the key is the only source. - _decorate_template_with_extension now stamps template['name'] with the component's legacy identity (name attr, falling back to class name). In-tree palette entries expose it as the dict key; ext entries are keyed by namespaced id, so without this both alias maps lose the only bridge from node types like 'AstraDB' to the current template. Without these, the starter-project updater could not match moved-provider nodes, so their embedded code stayed one whitespace change behind the bundle sources -- every such flow showed the update-all banner, and because tool-mode nodes' saved outputs ([component_as_tool]) differ from the template's natural outputs the update was flagged breaking, opening the confirmation modal instead of toasting 'successfully updated': deterministic timeouts in the starter-project Playwright specs (shards 29/30/36/37). Regenerated the 13 affected starter projects with scripts/ci/update_starter_projects.py (idempotent; code_hash hex strings in the regenerated JSONs are detect-secrets false positives). Also converts test_groq_integration.py to the captured-module patch.object pattern (same module-identity trap as test_mongodb_atlas: dotted-name @patch can land on a different materialization of the moved module than the one that defined the class, leaving the real get_groq_models in the instance's globals -- order-dependent failure in unit-test Group 5).
This comment has been minimized.
This comment has been minimized.
The starter-project updater syncs node metadata from the live templates
(NODE_FORMAT_ATTRIBUTES includes 'metadata'), and ext components report
their runtime sys.modules namespace there (_lfx_ext.official.<p>.<m>) --
a path that only exists inside a running extension loader. Persisting it
broke test_template_field_order_matches_component in both template jobs
('No module named _lfx_ext'): the test imports metadata.module to
instantiate the component, and the migration table likewise keys on the
legacy lfx.components.<provider> form that the bundle shims keep
importable.
_merge_node_metadata now preserves the node's stored module when the
live template's is a runtime _lfx_ext.* path, while still syncing the
rest of the metadata (e.g. dependencies). Starter projects regenerated
from the pre-pollution state with the fixed updater: the only diff vs
the previous regen is the 20 module lines reverting to the legacy
importable form. Regression tests added for both merge directions.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
* Add Oracle Integration * [autofix.ci] apply automated fixes * Fix styleUtils problem * fix(oracledb): address review feedback * fix(oracledb): apply formatting * Fix component index * refactor(oracledb): convert Oracle integration to lfx-oracle extension bundle Port the in-tree lfx.components.oracledb provider into a standalone Extension Bundle distribution (src/bundles/oracle, dist lfx-oracle, package lfx_oracle, bundle name 'oracle') per src/bundles/PORTING.md: - Move the four components + connection helper into the bundle; deps (oracledb, langchain-oracledb, langchain-community) move from the langflow-base[oracledb] extra into the bundle's pyproject - Add migration-table entries mapping legacy bare class names and lfx.components.oracledb.* import paths to ext:oracle:<Class>@official - Add test_pilot_oracle_upgrade.py integration coverage (mirrors ibm) - Move backend unit tests into src/bundles/oracle/tests with bundle import paths; drop the empty ComponentTestBaseWithoutClient version-fixture scaffolding (component is new in 1.11.0) - Declare explicit outputs on OracleVectorStoreComponent (mirrors LCVectorStoreComponent) so lfx extension validate passes - Regenerate component index (oracledb category removed) and uv.lock; wire workspace member/dep/source in root pyproject - Frontend sidebar entry renamed oracledb -> oracle; docs page renamed to bundles-oracle following the extracted-bundle convention --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Hare <ericrhare@gmail.com>
|
Build successful! ✅ |
Bundle Separation Phase A — metapackage split (1.11) · meta PR
This is the single PR for the entire Phase A bundle-separation epic. The original 10-PR stack has been fully collapsed into this branch as each ticket passed review — #13564, #13565, #13566, #13567, and #13568 merged here directly, and #13568 itself carried #13573, #13576, #13577, #13578, #13579, and #13580. Only this PR remains to merge into
release-1.11.0.Stacked on this PR: #13502 (Oracle integration) is based on this branch — it arrives as a sixth graduated
lfx-oraclebundle following the same conventions (manifest-shipping, bounded ranges,.dev0lfx floor, migration-table entries, pilot-upgrade test) and merges after this PR lands.What it does:
lfxbecomes engine-only. The ~45-provider long tail moves into one manifest-lesslfx-bundlesmetapackage discovered via a newlfx.bundlesentry-point group, and five partner/flagship providers graduate to standalone manifest-shipping packages:lfx-openai,lfx-anthropic,lfx-amazon,lfx-datastax,lfx-cohere. Zero user-facing change:ext:<provider>:<Class>@officialcomponent ids are unchanged, in-tree shims keepfrom lfx.components.<provider> import Xworking, and existing flows load and run identically.~328 files changed (+39k / −59k with rename detection; most of the delta is providers relocating from
src/lfx/src/lfx/components/intosrc/bundles/).Part 1 — manifest-less
lfx.bundlesdiscovery + precedence tier (foundation)load_lfx_bundles_extensions(src/lfx/src/lfx/extension/loader/_bundles_root.py) iterates thelfx.bundlessetuptools entry-point group, resolves each declared package viafind_spec, folder-walks its immediate subdirectories, and registers each as a bundle at the@officialslot with noextension.json(the langchain-community model). Reuses_load_bundle_directory, mirrorsdiscover_inline_bundles.installed > seed > lfx_bundles > dev > inline. Manifest always wins: a manifest-shippinglfx-<provider>shadows the same-named metapackage provider with a typedbundle-shadowedwarning, and the shadowed provider is never imported — this is what lets a provider graduate with no lockstep release.bundle-discovery-malformederror code, emitted onwarnings(never flipsok/ aborts startup), with per-mode codes for unresolvable declarations, plain-module targets, namespace-package portions, and invalid provider folder names. Manifest-less bundles get a typed reload refusal instead of a silent no-op.Part 2 —
lfx-bundlesmetapackage skeleton (#13564) + 45-provider move (#13568)src/bundles/lfx-bundles/: singlepyproject.tomldeclaring thelfx.bundlesentry-point, a pre-release-safelfx>=1.11.0.dev0,<2.0.0floor, a generatedallextra, and a README documenting the model + install stories. Hyphen dir name sorelease.yml'ssrc/bundles/*/pyproject.tomlglob builds it with zero workflow change.src/lfx/src/lfx/components/intosrc/bundles/lfx-bundles/src/lfx_bundles/, each discovered manifest-less at@official. In-tree marker shims preserve the old import paths; the migration table andcomponent_index.jsonare updated append-only/surgically with sha256 re-verified.scripts/ci/update_bundle_versions.py(+ tests) — the nightly bundle-rename track it served was retired by the canonical pre-release cutover (src/bundles/NIGHTLY.md).Part 3 — engine-only
lfx+lfx[bundles]extra (#13565)pip install lfx→ engine only;pip install "lfx[bundles]"→ engine + thelfx-bundleslong tail. Intentionally nolfx[all].lfx-bundlesappears only underextra == "bundles", never in coreRequires-Dist.Part 4 — partner graduations (#13573)
lfx-openai,lfx-anthropic,lfx-amazon,lfx-datastax,lfx-cohere: each shipsextension.json(lfx.compat: ["1"]), alangflow.extensionsentry-point, anlfxpin, and is a workspace member pinned by langflow. Partner set is disjoint fromlfx-bundles(verified).lfx.base.datastax→lfx_datastax.base, 74 backend tests move with it, 10 repo consumers rewritten (incl. thevector_store_ragstarter project).validate_extensionnow accepts classes whose base is a derived Component base (LCVectorStoreComponentetc.) that inherit class-leveloutputs.lfx-*package names — no third-party package added or removed.Part 5 — CI: cross-bundle matrix (#13566) + components freeze gate (#13567)
.github/workflows/cross-bundle-test.yml: per (bundle × Python 3.10/3.13) — installs in-repolfx+ the bundle, imports the entry-point package, assertslfx.bundlesdiscovery is error-free, runslfx extension validatefor manifest bundles, and runs the bundle's owntests/. Discovers bundles via the same globrelease.ymluses, so each new extraction self-registers. Triggers:pull_request,workflow_dispatch, weeklyschedule,workflow_call.scripts/ci/check_components_frozen.py+ baselinefrozen_component_dirs.txt: no new top-level provider directory may be added tosrc/lfx/src/lfx/components/— new providers go tolfx-bundlesor a graduated package. Removals are allowed (shim cleanup never trips it). Wired intoextension-migration-checks.ymlso it actually runs on PRs (re-homed fromlint-py.ymlduring review).Part 6 — hardening + review follow-ups (#13576–#13580 and branch commits)
lfx-*packages (chore(bundles): bounded version ranges for curated lfx-* packages #13576); shim-contract lock tests + breakage audit (test(bundles): lock the in-tree shim contract + breakage audit #13577); bundle docs — install shapes, override rule,bundle_api_version(docs(bundles): install shapes, override rule, bundle_api_version #13578); stack-review fixes — symlink containment, idempotency, docs accuracy (fix(bundles): stack-review fixes — symlink containment, idempotency, docs accuracy #13579); tombstone for the broken legacyZepChatMemorybuild method (fix(bundles): tombstone the broken legacy ZepChatMemory build method #13580).loading.py/tableAutoCellRenderto base-branch state (out-of-scope changes reverted); frontend test updates.Test plan
@officialregistration, malformed-declaration / plain-module / namespace-portion / invalid-name warnings, manifest-shadows-manifestless, typed reload refusallfx_bundlesregisters its providers, disjoint from the partner setimport lfx.componentslfx extension validatepasses for all manifest bundles;component_index.jsonsurgically updated, sha256 verifieduv syncresolves; wheels build;lfxMETADATA carrieslfx-bundlesonly underextra == "bundles"Collapsed stack (all merged into this branch)
lfx-bundlesmetapackage skeletonlfx[bundles]extra, engine-onlylfxlfx/componentsfreeze gatelfx-*bundle_api_versionZepChatMemorybuildSummary by CodeRabbit